from DrissionPage import ChromiumPage, Chromium, ChromiumOptions
import time, logging, os, re
from datetime import datetime
from typing import List, Optional, Union
from cozepy import COZE_CN_BASE_URL, Coze, Message, TokenAuth

# 检查页面左侧是否有对话窗口
def listen_for_clients(kfmz):
    while True:
        print("切换标签开始")
        print("开始点击左侧客户")
        logging.info("切换标签开始")
        logging.info("开始点击左侧客户")
        page.refresh()
        time.sleep((1.5))
        # 依次点击左边的全部用户对话窗口
        dialogs = page.eles('@class=session-item-container')  # 替换为实际的 CSS 选择器
        if not dialogs:
            print("--------------------------------------")
            print("左侧不存在用户")
            logging.info("--------------------------------------")
            logging.info("左侧不存在用户")
            page.refresh()
            time.sleep(1.5)
            print("没有客户，刷新完成")
            logging.info("没有客户，刷新完成")
        elif dialogs:
            print("--------------------------------------")
            print("左侧存在用户")
            print("开始循环点击")
            logging.info("--------------------------------------")
            logging.info("左侧存在用户")
            logging.info("开始循环点击")
            for dialog in dialogs:
                dialog.click()
                # 处理各种情况
                print("开始处理情况")
                logging.info("开始处理情况")
                clqk()
                print("点击所有完成")
                logging.info("点击所有完成")
            # 处理完所有客户对话后刷新页面
            # page.refresh()
            # time.sleep(0.8)
        else:
            page.refresh()
            time.sleep(1.5)


# 处理各种情况
def clqk():
    # 判断会话结束
    pdhhjs()
    # 判断情况，如果是自己回答就跳过，如果是智能和客户就根据情况回答
    pdqk()


# 判断对话是否结束，如果最后一行是长时间不回答点击结束会话
def pdhhjs():
    # 等待页面加载完成，这里假设等待 3 秒，可根据实际情况调整
    page.wait.load_start(1.5)
    # 使用 XPath 的 last() 函数直接获取最后一个匹配元素
    last_element = page.ele('xpath://*[@id="chat-view"]/*[@class="chat-scroll-view"]/div[3]/div/div[last()]')
    # 如果成功定位到元素
    if last_element:
        # 检查 data-type 属性值是否为 5
        data_type = last_element.attr('data-type')
        if data_type == '5':
            print("会话已结束客户长时间不回答")
            print(f"data_type = {data_type}")
            logging.info("会话已结束客户长时间不回答")
            logging.info(f"data_type = {data_type}")
            tchh()
            print("已经成功退出会话")
            logging.info("已经成功退出会话")
        else:
            print(f"获取到的 data-type 属性值为: {data_type}")
            print('退出')
            logging.info(f"获取到的 data-type 属性值为: {data_type}")
            logging.info('退出')

    else:
        print('未找到匹配的元素')
        logging.info('未找到匹配的元素')


# 点击退出会话
def tchh():
    print("点击退出回话")
    logging.info("点击退出回话")
    page.ele('xpath://*[@class="func-wrap bold"][2]').click()
    print("点击成功")
    logging.info("点击退出回话")


# 判断情况，如果是自己回答就跳过，如果是智能和客户就根据情况回答
def pdqk():
    # 获取所有消息元素
    sydh = page.eles('xpath://*[@class="message-time right" or @class="message-time left"]')

    # 检查列表是否为空
    if not sydh:
        print("未找到消息元素，可能页面加载不完全或 XPath 有误")
        logging.info("未找到消息元素，可能页面加载不完全或 XPath 有误")
        return  # 直接返回，避免后续错误

    # 获取最后一条消息
    last_message = sydh[-1].text
    print(f"获取最后一条消息: {last_message}")
    logging.info(f"获取最后一条消息: {last_message}")

    # 判断消息来源
    if '人工客服马上为你提供服务' in last_message:
        print("是智能助手的回复，执行相应操作")
        logging.info("是智能助手的回复，执行相应操作")
        znzshh()  # 处理智能助手回复的函数
    elif kfmz in last_message:
        print('是自己的回复，退出')
        logging.info('是自己的回复，退出')
    else:
        print('是客户的消息，执行相应操作')
        logging.info('是客户的消息，执行相应操作')
        khhd()  # 处理客户消息的函数


# 是智能助手的回答
def znzshh():
    # 等待页面加载完成，这里假设等待 3 秒，可根据实际情况调整
    page.wait.load_start((1.5))
    # 使用 XPath 的 last() 函数直接获取最后一个匹配元素
    last_element = page.ele('xpath://*[@id="chat-view"]/*[@class="chat-scroll-view"]/div[3]/div/div[last()-1]')
    # 如果成功定位到元素
    if last_element:
        # 检查 data-type 属性值是否为 1 和 10
        data_type = last_element.attr('data-type')
        print(f"当前 data-type 属性值为 {data_type}")
        if data_type in ['5', '-1', '6']:
            print("系统提示")
            logging.info("系统提示")
            return None
        elif data_type not in ['1', '10']:
            print("图片或视频类型")
            logging.info("图片或视频类型")
            page.ele('@class=text-area-wrap').input('亲,网络不好我们这里看不到图片和视频您能文字描述一下吗\n')
        else:
            print("文字或语音类型")
            logging.info("文字或语音类型")
            # 2只是正常的，那么忽略智能回答把前面所有的结果总结起来发给扣子
            kzhd()
    else:
        print('未找到匹配的元素')
        logging.info('未找到匹配的元素')


# 客户回答
def khhd():
    # 等待页面加载完成，这里假设等待 3 秒，可根据实际情况调整
    page.wait.load_start((1.5))
    # 使用 XPath 的 last() 函数直接获取最后一个匹配元素
    last_element = page.ele('xpath://*[@id="chat-view"]/*[@class="chat-scroll-view"]/div[3]/div/div[last()]')
    # 如果成功定位到元素
    if last_element:
        # 检查 data-type 属性值是否为 1 和 10
        data_type = last_element.attr('data-type')
        if data_type in ['6']:  # 如果对话框是人工客服转接则不回复
            return None
        elif data_type not in ['1', '10']:
            page.ele('@class=text-area-wrap').input('亲,网络不好我们这里看不到图片和视频您能文字描述一下吗\n')
        else:
            print(f"当前 data-type 属性值为 {data_type}，是文字或语音类型")
            logging.info(f"当前 data-type 属性值为 {data_type}，是文字或语音类型")
            # 2只是正常的，那么忽略智能回答把前面所有的结果总结起来发给扣子
            kzhd()
    else:
        print('未找到匹配的元素')
        logging.info('未找到匹配的元素')


# 把所有文字和语音整理发送扣子
def kzhd():
    page.wait.load_start((1.5))
    # 查找主元素
    main_element = page.eles('xpath://*[@id="chat-view"]/*[@class="chat-scroll-view"]/div[3]/div/div')
    print("找到总元素")
    print(len(main_element))
    logging.info("找到总元素")
    logging.info(len(main_element))
    zdh = []

    for i in main_element:
        data_type = i.attr('data-type')
        if data_type in ['1', '10']:
            zdh.append(i.text)
    print(f'zdh={zdh}')
    print(f'最后一个元素：{re.search(r"正在接入中.*",zdh[-1])}')
    logging.info(f'zdh={zdh}')
    logging.info(f'最后一个元素：{re.search(r"正在接入中.*",zdh[-1])}')
    # else:
    # 提取文字
    hh = tqwz(zdh)
    print(hh)
    hh2 = zcw(hh)
    print(hh2)
    hh3 = gszh(hh2)
    # print(hh3)
    print(f"文字提取完成：{hh3}")
    logging.info(f"文字提取完成：{hh3}")

    # 将hh3用户回复的最后一个Message.build_user_question_text()提取返回
    def extract_last_question(hh3):
        for item in reversed(hh3):
            match = re.search(r'Message\.build_user_question_text\("([^"]*)"\)', item)
            if match:
                return match.group(1)
        return None

    result = extract_last_question(hh3)
    print(f"取最后一条用户发送的消息：{result}")
    logging.info(f"取最后一条用户发送的消息：{result}")

    # 判断是否是时间格式，如果是纯emoji表情页面元素会有变化，抓取信息时只能取到时间值
    def is_time_format(result):
        pattern = r'^([01]?[0-9]|2[0-3]):[0-5][0-9]$'  # 匹配 "HH:MM" 格式
        return bool(re.match(pattern, result))

    # 如果用户发送emoji表情，直接回复🌹🌹
    if is_time_format(result):
        print("用户消息为emoji，回答🌹🌹")
        logging.info("用户消息为emoji，回答🌹🌹")
        # time.sleep(1.5)
        response = "[玫瑰][玫瑰]"

    # 如果取值出现问题，出现【14:24 14:25Scorpio】这种格式则不回复消息
    elif kfmz in result:
        return None

    else:
        response = api.chat(
            user_id=user_id,
            messages=hh3
        )
        print(f"ai回答：{response}")
        logging.info(f"ai回答：{response}")

    page.ele('@class=text-area-wrap').input(response + '\n')
    time.sleep(1)

    # 增加判断，如果用户有售后需求，自动转接人工客服，判断标志为’|’
    if '丨' in response:
        button_zhuanjie = page.ele('x://*[@id="app"]/div[2]/div/div[2]/div[3]/div[1]/div/div[1]/div[2]/div[1]')
        print(f"用户要求转接售后，转人工客服操作，操作按钮：{button_zhuanjie}")
        logging.info(f"用户要求转接售后，转人工客服操作，操作按钮：{button_zhuanjie}")
        button_zhuanjie.click()
        time.sleep(0.5)
        # 点击客服
        button_kefux = page.ele('x://*//div[7]/div/div/div/div/div[2]/div')
        button_kefu_namex = page.ele('x://div[7]/div/div/div/div/div[2]/div/div/span')
        button_kefu_name = button_kefu_namex.text
        button_kefux.click()
        print(f"客服：{button_kefu_name}")
        logging.info(f"用户要求转接售后，转人工客服操作，操作按钮：{button_kefu_name}")
        time.sleep(0.5)
        # 点击提交
        button_tijiao = page.ele('x://div[7]/div/div/div/div/div[4]/div[2]/button')
        button_tijiao.click()
        print("提交转接")
        logging.info("提交转接")


# 把提取出来的文字转成去掉时间
def tqwz(zdh):
    formatted = []

    for msg in zdh:
        # 按最后一个\n分割发送者和内容
        parts = msg.rsplit('\n', 1)

        if len(parts) == 1:
            # 没有\n，整个消息都是内容，默认为客户
            sender = "客户"
            content = parts[0].strip()
        else:
            # 分割出发送者和内容
            sender_part, content = parts
            content = content.strip()

            # 判断发送者是否包含客服标识
            if any(keyword in sender_part for keyword in [kfmz, "智能助手"]):
                sender = "我"
            else:
                sender = "客户"

        # 格式化并添加到结果列表
        if content:
            formatted.append(f"{sender}:{content}")

    return formatted


# 再转换一下不是客户的全转成我
def zcw(hh):
    formatted = []
    for msg in hh:
        if msg.startswith('客户:'):
            # 客户消息：保留客户标签，移除双引号
            content = msg.replace('客户:"', '客户:').replace('"', '')
            formatted.append(content)
        else:
            # 非客户消息：统一改为"我:"，移除双引号
            content = msg.split(':', 1)[1].replace('"', '').strip()
            formatted.append(f"我:{content}")
    return formatted


# 会话转换成扣子可以理解的一问一答
def gszh(conversation):
    # 预处理：如果第一条消息是客服的，删除它
    if conversation and conversation[0].startswith("我:"):
        conversation = conversation[1:]

    merged = []
    i = 0

    while i < len(conversation):
        current_item = conversation[i]
        sender_prefix = "客户:" if current_item.startswith("客户:") else "我:"

        # 如果当前消息是客户或客服，提取内容并合并
        if current_item.startswith(sender_prefix):
            current_content = current_item[len(sender_prefix):].strip()
            if not current_content:
                i += 1
                continue

            count = 1
            # 查找所有连续的相同发送者消息
            while i + count < len(conversation) and conversation[i + count].startswith(sender_prefix):
                next_content = conversation[i + count][len(sender_prefix):].strip()
                if next_content:
                    current_content += " " + next_content
                count += 1

            merged.append(f"{sender_prefix}{current_content}")
            i += count
        else:
            # 非标准格式消息，直接添加
            merged.append(current_item)
            i += 1

    result = []
    for i, item in enumerate(merged):
        # 移除"客户:"或"我:"前缀
        content = item
        if content.startswith("客户:"):
            content = content[3:].strip()
        elif content.startswith("我:"):
            content = content[2:].strip()

        # 根据奇偶位置决定消息类型
        if i % 2 == 0:
            result.append(f'Message.build_user_question_text("{content}")')
        else:
            result.append(f'Message.build_assistant_answer("{content}")')

    return result


class CozeChatAPI:
    """Coze API 聊天封装类"""

    def __init__(self, api_token: Optional[str] = None,
                 bot_id: Optional[str] = None,
                 base_url: Optional[str] = None):
        """
        初始化 Coze API 客户端

        Args:
            api_token: Coze API 令牌，如果为 None 则从环境变量 COZE_API_TOKEN 获取
            bot_id: 机器人 ID，如果为 None 则从环境变量 COZE_BOT_ID 获取
            base_url: API 基础 URL，如果为 None 则使用默认值
        """
        # 获取 API 令牌
        self.api_token = api_token or os.getenv("COZE_API_TOKEN")
        if not self.api_token:
            raise ValueError("请提供 Coze API 令牌或设置 COZE_API_TOKEN 环境变量")

        # 获取机器人 ID
        self.bot_id = bot_id or os.getenv("COZE_BOT_ID")
        if not self.bot_id:
            raise ValueError("请提供机器人 ID 或设置 COZE_BOT_ID 环境变量")

        # 设置 API 基础 URL
        self.base_url = base_url or COZE_CN_BASE_URL

        # 初始化 Coze 客户端
        self.coze = Coze(auth=TokenAuth(token=self.api_token), base_url=self.base_url)

    @staticmethod
    def process_messages(raw_messages: List[str]) -> List[Message]:
        """
        处理原始消息列表，转换为 Coze Message 对象

        Args:
            raw_messages: 原始消息列表，格式为 'Message.build_user_question_text("内容")' 或
                          'Message.build_assistant_answer("内容")'

        Returns:
            转换后的 Message 对象列表
        """
        messages = []
        for msg in raw_messages:
            if 'build_user_question_text' in msg:
                content = msg.split('"')[1]  # 提取内容
                messages.append(Message.build_user_question_text(content))
            elif 'build_assistant_answer' in msg:
                content = msg.split('"')[1]
                messages.append(Message.build_assistant_answer(content))
        return messages

    def chat(self, user_id: str, messages: List[str],
             additional_messages: Optional[List[str]] = None) -> str:
        """
        与 Coze 机器人进行聊天

        Args:
            user_id: 用户 ID，标识用户身份
            messages: 消息列表，格式为 'Message.build_user_question_text("内容")'
            additional_messages: 额外的消息历史，用于上下文对话

        Returns:
            机器人的回复内容
        """
        # 处理消息
        processed_messages = self.process_messages(messages)

        # 处理额外消息（如果有）
        processed_additional = []
        if additional_messages:
            processed_additional = self.process_messages(additional_messages)

        # 调用 Coze API
        chat_poll = self.coze.chat.create_and_poll(
            bot_id=self.bot_id,
            user_id=user_id,
            additional_messages=processed_additional + processed_messages
        )

        # 返回机器人的回复
        return chat_poll.messages[0].content if chat_poll.messages else ""


# 配置日志文件
current_date = datetime.now().strftime("%Y-%m-%d")  # 获取当天日期
log_filename = f"微信小店ai客服测试日志_{current_date}.log"

logging.basicConfig(
    filename=log_filename,  # 日志文件名
    level=logging.INFO,  # 日志级别（INFO\DEBUG\WARNING\ERROR等）
    format='%(asctime)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s',  # 日志格式
    datefmt='%Y-%m-%d %H:%M:%S'  # 时间格式
)

if __name__ == "__main__":
    # 启动或接管浏览器，并获取标签页对象
    kfmz = 'Scorpio'
    # 方法 1：通过环境变量设置
    os.environ["COZE_API_TOKEN"] = "pat_kTzG7ghrGq8S2D7aWRMkzmIMNVCU7uRMySdcPlQtMLHDh8Gm9km6JwWVLmXAIchL"
    os.environ["COZE_BOT_ID"] = "7503845569832501282"
    api = CozeChatAPI()
    user_id = "123456"
    logging.info(f"客服名字：{kfmz}，COZE_API_TOKEN = {os.environ['COZE_API_TOKEN']}，COZE_BOT_ID = {os.environ['COZE_BOT_ID']}，user_id = {user_id}")

    co = ChromiumOptions().auto_port()
    co.set_argument('--start-maximized')    # 浏览器最大化
    browser = ChromiumPage(addr_or_opts=co)

    logging.info("+++++++++++++++++++++++++程序开始执行，建立链接：'https://store.weixin.qq.com/shop?redirect_url=%2Fkf'+++++++++++++++++++++++++")
    browser.get('https://store.weixin.qq.com/shop?redirect_url=%2Fkf')  # 标签页直接访问客服对话框
    # # 记录当前网页地址
    # current_url = browser.url
    page = browser.latest_tab  # 获取最后激活的标签页对象
    print('请扫码登录......')
    logging.info('请扫码登录......')
    # 检测网页变化，当跳转至客服页面后自动向下执行代码
    target_url = "https://store.weixin.qq.com/shop/kf"
    while True:
        if "shop/kf" in page.url:
            break

    print(f"登录成功！URL已跳转：{page.url}")
    logging.info(f"登录成功！URL已跳转：{page.url}")

    while True:
        try:
            listen_for_clients(kfmz)  # 持续执行目标函数
        except Exception as e:
            # 如果页面关闭，程序会高速重复执行并报错，直接结束程序
            if '与页面的连接已断开' in str(e):
                print(f"执行出错: {e}，停止程序")
                logging.error(f"执行出错: {e}，停止程序")
                break
            print(f"执行出错: {e}")  # 打印错误信息
            print("重新执行")  # 按要求输出"重新执行"
            logging.error(f"执行出错: {e}，重新执行")

        # 无需finally块，循环会自动继续
